package cs340omega;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket; 
import java.net.UnknownHostException;
/**
 * here is the Server side class.
 * It is a Thread that always running for hearing request from other node.
 * @author djb0646
 * @version 0.9
 */
public class Server extends Thread
{
	

	private ServerSocket hearingPort;  
	private volatile boolean run = true;
	private ObjectDB database;
	private final int defaultPort = 3000;
	private int usingPort = defaultPort;
	private final int defaultOutPort = 3001;
	private final int SLEEP_100 = 100;
	private final int SLEEP_5000 = 5000;
	
	/**
	 * constructor of Server()
	 * create a hearing port 3000 for hearing the incoming message.
	 * 
	 */
	public Server()
	{   
		
		try 
		{ 
			
			hearingPort = new ServerSocket(usingPort);
		
		//	System.out.println("initaddr"+hearingPort.getInetAddress());
			
			database = new ObjectDB();
				//System.out.println(database.toString());
				//database.finalize();
			
		} catch (IOException e) 
		{
		
		}
	}
	/**
	 * 
	 * @param port
	 * port that we want to hearing requests
	 */
	public Server(int port)
	{   
		
		try 
		{  
			usingPort = port;
			hearingPort = new ServerSocket(usingPort);
		
		//	System.out.println("initaddr"+hearingPort.getInetAddress());
			
			database = new ObjectDB();
				//System.out.println(database.toString());
				//database.finalize();
			
		} catch (IOException e) 
		{
			e.printStackTrace();
		}
	}
	/**
	 * (non-Javadoc)
	 * @see java.lang.Thread#run()
	 */
	public void run()
	{  
		System.out.println("|server|->run()");
		 
		while (run) 
		{ 
			try
			{
		       // System.out.println("Waiting for connections.");
		         
		    	  
				listenData();
				Thread.sleep(SLEEP_100);
		    		   
		    	   
			} 
			catch (IOException e) 
			{
		    	;  
			} 
			catch (ClassNotFoundException e) 
			{
				;
			}
			catch (InterruptedException e) 
			{
				;
			}
			//	System.out.println("Run is: " + run);
		}
		
		System.out.println("|server|Server stopped!");
		
		try
		{ 
			if(hearingPort!=null && !hearingPort.isClosed())
			{
				hearingPort.close();
			}
		}
		catch (IOException e)
		{
			;
		}
		
		//stop(); -- depreciated method when the run method has already been terminated
		 
	}
	
	/**
	 * the listening method:
	 *<br>
	 *  1.hearing request from client
	 *<br>
	 * 2.get a port number for specified client
	 *<br>
	 * 3.waiting one new port for the command object
	 * <br>
	 * 4. processing request and send result back to client.
	 */
	public void listenData()  throws IOException, ClassNotFoundException
	{	
		//System.out.println("|server|listening incoming data"); 
		//System.out.println("|server|next line");
		if(!run)
		{
			return;
		}
		if(hearingPort==null||!hearingPort.isBound()||hearingPort.isClosed())
		{
			hearingPort = new ServerSocket(usingPort);
		}
		Socket source = hearingPort.accept();
		
		//System.out.println("|server|line after"); 
		
		System.out.println("|server|new requst from" + source.getRemoteSocketAddress());
		ObjectOutputStream output = new ObjectOutputStream(source.getOutputStream()); 
		ObjectInputStream input = new ObjectInputStream(source.getInputStream());
		
		String newLink = (String) input.readObject(); 
		ServerSocket temp = new ServerSocket(Integer.valueOf(newLink));
	 	output.writeObject(new String("|Server|new port " + newLink
	 						+ " is ready to get remote data from "
	 						+ source.getRemoteSocketAddress()));
	
	 	output.flush();
		output.close();
		input.close(); 
		source.close();
		listener l = new listener(this, temp, source); 
		l.start();
	//	temp.setSoTimeout(15000);
		source = temp.accept(); 
		l.interrupt();
		temp.close();
		individualLink(source);	
		
	} 
	/**
	 * @param source
	 * the socket waiting for command object
	 * create a new link, get data form link, 
	 * process it ,
	 *  send back.
	 * @exception
	 * linking problems and processing problems
	 */
	public void individualLink(Socket source)throws IOException, ClassNotFoundException
	{	
		//System.out.println("|server|setting up individualLink");
		ObjectOutputStream output = new ObjectOutputStream(source.getOutputStream()); 
		ObjectInputStream input = new ObjectInputStream(source.getInputStream());
		
		System.out.println("|server|hearing port " + source.getLocalPort()
							+ " form remote ip " + source.getRemoteSocketAddress());
	
		output.writeObject(processingRequest(input.readObject()));
		
		System.out.println("|server|return data");
		
		output.flush();
		output.close();
		input.close(); 
		source.close();
		
		System.out.println("|Server|request ended\n");
		
	}
	/** 
	 * process the received object
	 * @param command
	 * the command for what to do
	 * 
	 */
	public Object processingRequest(Object command)
	{
		System.out.println("|Server|..Analysing and processing data");
		if(command instanceof String)
		{
			return command;
		} 
		else if(command instanceof Command)
		{
			
			System.out.println("|Server|I got a command ");
			
			String result = command.toString();
			result = "\t" + result.replace("\n", "\n\t");
			System.out.println(result);
			
			Object obj = database.get(((Command) command).getObjID());
			//System.out.println(database.toString());
			
			if (obj == null)
			{
				System.out.println("|Server|Unable to locate object");
			}
			else
			{
				System.out.println("|Server|Located object in database");
				try
				{
					if (((Command) command).returns())
					{
						return ((Command) command).invoke(obj);
					}
					else
					{
						((Command) command).invoke(obj);
					}
				} 
				catch (InvocationTargetException e)
				{
					e.printStackTrace();
				}
				catch (NoSuchMethodException e)
				{
					e.printStackTrace();
				}
				catch (IllegalAccessException e)
				{
					e.printStackTrace();
				}
				catch (IllegalArgumentException e)
				{
					e.printStackTrace();
				}
			}
			
		}
		else if(command instanceof Port)
		{
			System.out.println("|Server|resceive Port:" + ((Port) command).getPort());
		}
		else
		{
			System.out.println("\n|Server|Warning: someone give me a piece of junk!\n");
		}
		
		return null;
		
		
	}
	/**
	 * Important method that halt the server from running 
	 * send a message to itself and terminate sockets which still waiting.
	 */
	public void done()
	{
		run = false;
		 
		try
		{
			System.out.println("|server|Stopping server");
			
			try
			{
				Client.sendData(InetAddress.getLocalHost().getHostAddress() 
								, defaultOutPort, "Kill the server!!!");
			} catch (UnknownHostException e) 
			{
				;
				 
			}
			catch (IOException e)
			{
				;
				 
			}
			
		}
		catch (ClassNotFoundException e)
		{
			e.printStackTrace();
		}  
		
	}
	public static void main(String[] args)
	{
		Server s = new Server();
		s.start();
	}
	/**
	 * 
	 * @author djb0646
	 * this is a class that tend to halt another thread after a time period
	 *
	 */
	private class listener extends Thread
	{
		private Thread other;
		private ServerSocket temp;
		private Socket socket;
		public listener(Thread other, ServerSocket temp, Socket socket)
		{
			this.other = other;
			this.socket = socket;
			this.temp = temp;
		}
		public void run() 
		{
			try
			{ 
				System.out.println("|listener|time counting");
				Thread.sleep(SLEEP_5000);
				//if(socket.getPort()==0){ 
				//socket.close();

				System.out.println("|listener| alive?");
				if(other.isAlive())
				{
					System.out.println("|listener| alive");
					hearingPort.close();
					socket.close();
					temp.close();
					other.interrupt();  
				}
			} catch (InterruptedException e) 
			{ 
				;
			}   
			catch (IOException e) 
			{
				e.printStackTrace();
			} 
		}
	}

}
